//
//  LinkLabel.swift
//  Lease_IOS
//
//  Created by Mr.wang on 2018/12/24.
//  Copyright © 2018 Mr.wang. All rights reserved.
//

import UIKit

typealias labelClickBlock = (Any?) -> Void

class LinkLabel: UILabel {
    
    var clickBlock : labelClickBlock = { _ in }
    var highLightsArray = [HighLight]()
    var highLight: HighLight?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.isUserInteractionEnabled = true
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let point = touch?.location(in: self)
        for highlight in self.highLightsArray {
            for value in highlight.positions! {
                let rect = value.cgRectValue
                if rect.contains(point!) {
                    let string = self.attributedText?.mutableCopy() as! NSMutableAttributedString
                    string.setTextColor(highlight.highlightColor!, in: highlight.range!)
                    string.setTextBackgroundColor(#colorLiteral(red: 0.862745098, green: 0.862745098, blue: 0.862745098, alpha: 1), in: highlight.range!)
                    self.attributedText = string
                    self.highLight = highlight
                    break
                }
            }
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let highLight = self.highLight else { return }
        let string = self.attributedText?.mutableCopy() as! NSMutableAttributedString
        string.setTextColor(highLight.linkColor!, in: highLight.range!)
        string.setTextBackgroundColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 0), in: highLight.range!)
        self.attributedText = string
        clickBlock(highLight.data)
        self.highLight = nil
    }
    
    func addLink(range: NSRange,
                 data: Any? = nil,
                 linkColor: UIColor = UIColor.blue,
                 highlightColor: UIColor = UIColor.blue) {
        self.sizeToFit()
        self.layoutIfNeeded()
        
        var highlight = HighLight()
        highlight.range = range
        highlight.linkColor = linkColor
        highlight.highlightColor = highlightColor
        highlight.data = data
        highlight.positions = self.highlightPositionsIn(selectRange: range)
        highLightsArray.append(highlight)
        
        let string = self.attributedText?.mutableCopy() as! NSMutableAttributedString
        string.setTextColor(linkColor, in: range)
        self.attributedText = string
    }
    
    func addLink(text: String,
                 data: Any? = nil,
                 linkColor: UIColor = UIColor.blue,
                 highlightColor: UIColor = UIColor.blue) {
        guard let range = self.attributedText?.string.range(of: text) else { return }
        self.addLink(range: text.toNSRange(range), data: data, linkColor: linkColor, highlightColor: highlightColor)
    }
    
    func highlightPositionsIn(selectRange: NSRange) -> [NSValue]{
        let attributedText = self.attributedText?.mutableCopy() as! NSAttributedString
        let ctFrameSetter = CTFramesetterCreateWithAttributedString(attributedText)
        var cfRange = CFRangeMake(0, CFIndex(attributedText.length))
        let suggestSize = self.getSuggetSizeAndRange(framesetter: ctFrameSetter,
                                                     attributedString: attributedText,
                                                     rangeToSize: &cfRange)
        
        let suggestPath = CGMutablePath()
        suggestPath.addRect(CGRect(x: 0, y: 0, width: self.bounds.size.width, height: suggestSize.height))
        
        var positions = [NSValue]()
        let ctFrame = CTFramesetterCreateFrame(ctFrameSetter, cfRange, suggestPath, nil)
        let lines = CTFrameGetLines(ctFrame)
        let count = CFArrayGetCount(lines)
        if count == 0 {
            return positions
        }
        
        let path = CTFrameGetPath(ctFrame)
        let boundsRect = path.boundingBoxOfPath
        let selectionStartPosition = selectRange.location
        let selectionEndPosition = NSMaxRange(selectRange)
        var origins = [CGPoint](repeating: CGPoint.zero, count: count)
        var transform = CGAffineTransform.identity
        transform = CGAffineTransform(translationX: 0, y: boundsRect.size.height)
        transform = transform.scaledBy(x: 1.0, y: -1.0)
        CTFrameGetLineOrigins(ctFrame, CFRangeMake(0,0), &origins)
        
        for i in 0..<count {
            let linePoint = origins[i]
            let line = unsafeBitCast(CFArrayGetValueAtIndex(lines, i), to: CTLine.self)
            let range = CTLineGetStringRange(line)
            
            var ascent: CGFloat = 0
            var descent: CGFloat = 0
            var leading: CGFloat = 0
            var width: CGFloat = 0
            var offset: CGFloat = 0
            var offset2: CGFloat = 0
            
            if self.wheather(position: selectionStartPosition, in: range) &&
                self.wheather(position: selectionEndPosition, in: range) {
                offset = CTLineGetOffsetForStringIndex(line, selectionStartPosition, nil)
                offset2 = CTLineGetOffsetForStringIndex(line, selectionEndPosition, nil)
                CTLineGetTypographicBounds(line, &ascent, &descent, &leading)
                let lineRect = CGRect(x: linePoint.x + offset,
                                      y: linePoint.y - descent,
                                      width: offset2 - offset,
                                      height:  ascent + descent)
                let rect = lineRect.applying(transform)
                
                let adjustRect = CGRect(x: rect.origin.x + boundsRect.origin.x,
                                        y: rect.origin.y + boundsRect.origin.y,
                                        width: rect.size.width,
                                        height: rect.size.height)
                positions.append(NSValue.init(cgRect: adjustRect))
                break
            }
            
            if self.wheather(position: selectionStartPosition, in: range) {
                offset = CTLineGetOffsetForStringIndex(line, selectionStartPosition, nil)
                width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading))
                let lineRect = CGRect(x: linePoint.x + offset,
                                      y: linePoint.y - descent,
                                      width: width - offset,
                                      height:  ascent + descent)
                let rect = lineRect.applying(transform)
                
                let adjustRect = CGRect(x: rect.origin.x + boundsRect.origin.x,
                                        y: rect.origin.y + boundsRect.origin.y,
                                        width: rect.size.width,
                                        height: rect.size.height)
                positions.append(NSValue.init(cgRect: adjustRect))
                
            } else if (selectionStartPosition < range.location &&
                
                selectionEndPosition >= range.location + range.length) {
                width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading))
                let lineRect = CGRect(x: linePoint.x ,
                                      y: linePoint.y - descent,
                                      width: width,
                                      height: ascent + descent)
                let rect = lineRect.applying(transform)
                
                let adjustRect = CGRect(x: rect.origin.x + boundsRect.origin.x,
                                        y: rect.origin.y + boundsRect.origin.y,
                                        width: rect.size.width,
                                        height: rect.size.height)
                positions.append(NSValue.init(cgRect: adjustRect))
                
            } else if (selectionStartPosition < range.location &&
                self.wheather(position: selectionEndPosition, in: range)) {
                
                offset = CTLineGetOffsetForStringIndex(line, selectionEndPosition, nil)
                width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, &leading))
                let lineRect = CGRect(x: linePoint.x ,
                                      y: linePoint.y - descent,
                                      width: offset,
                                      height: ascent + descent)
                let rect = lineRect.applying(transform)
                
                let adjustRect = CGRect(x: rect.origin.x + boundsRect.origin.x,
                                        y: rect.origin.y + boundsRect.origin.y,
                                        width: rect.size.width,
                                        height: rect.size.height)
                positions.append(NSValue.init(cgRect: adjustRect))
            }
            
        }
        return positions
    }
    
    func getSuggetSizeAndRange(framesetter: CTFramesetter,
                               attributedString: NSAttributedString,
                               rangeToSize: UnsafeMutablePointer<CFRange>?)  -> CGSize {
        let size = self.bounds.size
        let numberOfLines = self.numberOfLines
        let constraints = CGSize(width: size.width, height: CGFloat(MAXFLOAT))
        if numberOfLines > 0 {
            let path = CGMutablePath()
            path.addRect(CGRect(x: 0, y: 0, width: constraints.width, height: CGFloat(MAXFLOAT)))
            let frame = CTFramesetterCreateFrame(framesetter, CFRange(location: 0, length: 0), path, nil)
            let lines = CTFrameGetLines(frame)
            if (CFArrayGetCount(lines) > 0) {
                let lastVisibleLineIndex = min(CFIndex(numberOfLines), CFArrayGetCount(lines)) - 1
                let lastVisibleLine = unsafeBitCast(CFArrayGetValueAtIndex(lines, lastVisibleLineIndex), to: CTLine.self)
                let rangeToLayout = CTLineGetStringRange(lastVisibleLine)
                rangeToSize?.pointee = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length)
            }
        }
        let suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, (rangeToSize?.pointee)!, nil, constraints, nil)
        return CGSize(width: ceil(suggestedSize.width), height: ceil(suggestedSize.height))
    }
    
    func wheather(position: NSInteger,
                  in range: CFRange) -> Bool {
        return (position >= range.location && position < range.location + range.length)
    }
    
}

struct HighLight {
    var range: NSRange?
    var linkColor: UIColor?
    var highlightColor: UIColor?
    var data: Any?
    var positions: [NSValue]?
}

